using System.Reflection;
using System.Security.Claims;
using System.Text;
using FluentValidation.AspNetCore;
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Serilog;
using Serilog.Events;
using Swimj.AspNetCore.Infrastructure.Auth;
using Swimj.AspNetCore.Infrastructure.Logging;
using Swimj.AspNetCore.Options;
using Swimj.Core.Providers;
using Warehouse.ApplicationLib.Queries;
using Warehouse.CommonLib.Constants;
using Warehouse.DomainLib.AggregatesModel.WarehouseAggregate;
using Warehouse.InfrastructureLib;
using Warehouse.InfrastructureLib.Repositories;

namespace Warehouse.WebApi;

public class Program
{
    public static void Main(string[] args)
    {
        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
            .Enrich.FromLogContext()
            .WriteTo.Console()
            .CreateBootstrapLogger();

        Log.Information("Starting web host");


        var builder = WebApplication.CreateBuilder(args);
        builder.Host.UseSerilog((context, _, configuration) =>
            configuration.ReadFrom.Configuration(context.Configuration));
        // Add services to the container.
        builder.Services.AddHttpContextAccessor();
        builder.Services.AddCurrentUser();

        builder.Services.AddSingleton<IDateTimeProvider, LocalDataTimeProvider>();

        var defaultConnStr = builder.Configuration.GetConnectionString("Default");

        builder.Services.AddDbContext<WarehouseDbContext>(
            options =>
            {
                options.UseSqlServer(defaultConnStr, sqlOptions =>
                {
                    sqlOptions.MigrationsAssembly(typeof(Program).Assembly.FullName);

                    // Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency 

                    sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30),
                        errorNumbersToAdd: null);
                });
            }); //Showing explicitly that the DbContext is shared across the HTTP request scope (graph of objects started in the HTTP request)


        // 添加身份验证
        builder.Services
            .AddAuthentication(authentication =>
            {
                authentication.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                authentication.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(configureOptions =>
            {
                var key = builder.Configuration.GetValue<string>(
                    $"SecuritySettings:{nameof(JwtSettings)}:{nameof(JwtSettings.Key)}");

                configureOptions.RequireHttpsMetadata = false;
                configureOptions.SaveToken = true;
                configureOptions.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)),
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    RoleClaimType = ClaimTypes.Role,
                    ClockSkew = TimeSpan.Zero
                };
            });


        builder.Services.AddAuthorization(options =>
        {
            // Here I stored necessary permissions/roles in a constant
            foreach (var prop in typeof(ApiRoutes).GetNestedTypes().SelectMany(c =>
                         c.GetFields(BindingFlags.Public | BindingFlags.Static |
                                     BindingFlags.FlattenHierarchy)))
            {
                options.AddPolicy(prop.Name,
                    policy => policy.RequireClaim("Permission",
                        prop.Name));
            }
        });

        builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

        builder.Services.AddMediatR(cfg =>
        {
            cfg.RegisterServicesFromAssembly(Assembly.Load("Warehouse.ApplicationLib"));
        });

        builder.Services.AddScoped<IWarehouseRepository, WarehouseRepository>();
        builder.Services.AddScoped<IWarehouseQueries>(sp => new WarehouseQueries(defaultConnStr));

        builder.Services.AddControllers();


        builder.Services.AddHealthChecks()
            .AddCheck("self", () => HealthCheckResult.Healthy())
            .AddSqlServer(defaultConnStr ?? string.Empty,
                name: "ProductionDB-check",
                tags: new[] { "ProductionDB" });

        builder.Services.AddCors(options =>
        {
            options.AddPolicy("CorsPolicy",
                corsPolicyBuilder => corsPolicyBuilder
                    .SetIsOriginAllowed(_ => true)
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    .AllowCredentials());
        });

        builder.Services.AddFluentValidationAutoValidation();
        builder.Services.AddFluentValidationClientsideAdapters();

        builder.Services
            .AddRequestLogging(builder.Configuration)
            .AddExceptionMiddleware();


        // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
        builder.Services.AddEndpointsApiExplorer();
        builder.Services.AddSwaggerGen(c =>
        {
            c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
            {
                Name = "Authorization",
                In = ParameterLocation.Header,
                Type = SecuritySchemeType.ApiKey,
                Scheme = "Bearer",
                BearerFormat = "JWT",
                Description =
                    "Input your Bearer token in this format - Bearer {your token here} to access this API",
            });
            c.AddSecurityRequirement(new OpenApiSecurityRequirement
            {
                {
                    new OpenApiSecurityScheme
                    {
                        Reference = new OpenApiReference
                        {
                            Type = ReferenceType.SecurityScheme,
                            Id = "Bearer",
                        },
                        Scheme = "Bearer",
                        Name = "Bearer",
                        In = ParameterLocation.Header,
                    },
                    new List<string>()
                },
            });
        });

        var app = builder.Build();

        // Configure the HTTP request pipeline.
        if (app.Environment.IsDevelopment())
        {
            app.UseSwagger();
            app.UseSwaggerUI();
        }

// app.UseHttpsRedirection();
        app.UseCors("CorsPolicy");

        app.UseRouting();

        app.UseAuthentication();

        app.UseCurrentUser();

        app.UseAuthorization();

        app.MapControllers();

        app.MapHealthChecks("/hc", new HealthCheckOptions()
        {
            Predicate = _ => true,
            ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
        });

        app.MapHealthChecks("/liveness", new HealthCheckOptions
        {
            Predicate = r => r.Name.Contains("self")
        });

        app.Run();
    }
}